---
title: PdfAPI.Core business rules
summary: Enumerated business rules with per-rule trigger, enforcement, and outcomes.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-09-17
library: PdfAPI.Core
reference_type: package
---

# PdfAPI.Core business rules

This page captures the 45 business rules enforced by `PdfAPI.Core`, covering PDF signature validation, document/page-range lifecycle, page extraction, tracking-prefix encoding, and contract-level invariants read by consumers.

## BR-01 PDF signature validation

| Field | Value |
| --- | --- |
| **Statement** | A byte array is a PDF only when it is at least five bytes long, begins with the bytes `%PDF`, and its ASCII header starts with `"%PDF-"`. |
| **Trigger** | Any PDF inspection or transformation call |
| **Enforcement** | `PdfAPI.Core.PdfClientService.IsPdf` |
| **Outcome** | `true` / `false` |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-02 Null bytes are never a PDF

| Field | Value |
| --- | --- |
| **Statement** | A null byte array is never a valid PDF. |
| **Trigger** | Inspection with a null argument |
| **Enforcement** | `PdfAPI.Core.PdfClientService.IsPdf` |
| **Outcome** | Returns `false` |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-03 Invalid PDF input aborts with PdfException

| Field | Value |
| --- | --- |
| **Statement** | Invalid PDF input must abort the operation with a `PdfException` naming the file when a name is known. |
| **Trigger** | `ValidatePdf` guard fails |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ValidatePdf(byte[], string)` |
| **Outcome** | Throws `PdfException("ValidatePdf: The file '<name>' is not a valid pdf document.")` |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-04 Document validation requires a non-null document

| Field | Value |
| --- | --- |
| **Statement** | A document-level validation call must receive a non-null document. |
| **Trigger** | `ValidatePdf(Document, PdfOptions)` invoked |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ValidatePdf(Document, PdfOptions)` |
| **Outcome** | Throws `PdfException("No document was specified.")` |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-05 Document load requires in-memory or on-disk bytes

| Field | Value |
| --- | --- |
| **Statement** | A document is loadable when bytes are already in memory, or when its `FilePath` exists on disk. |
| **Trigger** | `Document.Load()` called |
| **Enforcement** | `PdfAPI.Core.Document.Load()` |
| **Outcome** | Returns `true` and populates `File`, or returns `false` |
| **Source** | `PdfAPI.Core\Document.cs` |

---

## BR-06 Failed document load aborts validation

| Field | Value |
| --- | --- |
| **Statement** | A document that cannot be loaded must abort validation. |
| **Trigger** | `Document.Load()` returns `false` |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ValidatePdf(Document, PdfOptions)` |
| **Outcome** | Throws `PdfException("The document could not be loaded.")` |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-07 Input file cleanup after load

| Field | Value |
| --- | --- |
| **Statement** | The on-disk input file is deleted after load unless options explicitly disable cleanup; a null `PdfOptions` is treated as cleanup enabled. |
| **Trigger** | Successful load during document validation |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ValidatePdf(Document, PdfOptions)`, reading `PdfOptions.CleanupInputFile` |
| **Outcome** | Source file deleted from disk |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-08 PDF version extraction

| Field | Value |
| --- | --- |
| **Statement** | The reported PDF version is the `d.d` value following `%PDF-`; when no such token is found the version is an empty string. |
| **Trigger** | `Version(byte[])` called |
| **Enforcement** | `PdfAPI.Core.PdfClientService.Version` |
| **Outcome** | Version string or `""` |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-09 Page count via marker scan

| Field | Value |
| --- | --- |
| **Statement** | The page count is the number of `/Type /Page` markers (excluding `/Pages`) in the decoded PDF markup. |
| **Trigger** | `GetPageCount(byte[])` called |
| **Enforcement** | `PdfAPI.Core.PdfClientService.GetPageCount` / `FindPageCount` |
| **Outcome** | Integer count; `0` for null text |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-10 Form field detection requires AcroForm

| Field | Value |
| --- | --- |
| **Statement** | A PDF has form fields only when it carries an AcroForm with at least one field. |
| **Trigger** | `HasFormFields(byte[])` called |
| **Enforcement** | `PdfAPI.Core.PdfClientService.HasFormFields` |
| **Outcome** | `true` / `false` |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-11 Flattening only applies with AcroForm

| Field | Value |
| --- | --- |
| **Statement** | Flattening is applied only when an AcroForm is present; a PDF without one is returned byte-for-byte unchanged. |
| **Trigger** | `Flatten(byte[])` called |
| **Enforcement** | `PdfAPI.Core.PdfClientService.Flatten` |
| **Outcome** | Flattened bytes, or the original bytes |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-12 Out-of-range page ranges are skipped

| Field | Value |
| --- | --- |
| **Statement** | A requested page range wholly beyond the end of the document is skipped rather than treated as an error. |
| **Trigger** | Extraction with `start > total` and `stop > total` |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ExtractPageRanges` |
| **Outcome** | Range contributes no pages; no exception |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-13 Page boundaries are clamped to page count

| Field | Value |
| --- | --- |
| **Statement** | A page boundary beyond the last page is clamped to the last page. |
| **Trigger** | Extraction with `start` or `stop` greater than the page count |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ExtractPageRanges` |
| **Outcome** | Boundary set to the total page count |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-14 Original range copies the whole document

| Field | Value |
| --- | --- |
| **Statement** | A range flagged as the original (start `1`, stop `-1`) copies the whole source document in one operation. |
| **Trigger** | `PageRange.IsOriginal` is true during extraction |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ExtractPageRanges` |
| **Outcome** | Entire document appended via `PdfCopy.AddDocument` |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-15 Open-ended stop page means through last page

| Field | Value |
| --- | --- |
| **Statement** | An open-ended stop page (`-1`) means "through the last page". |
| **Trigger** | Extraction of a range whose stop is `-1` and which is not the original range |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ExtractPageRanges` |
| **Outcome** | Pages appended from start through the last page |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-16 Descending range reverses page order

| Field | Value |
| --- | --- |
| **Statement** | A range whose stop page precedes its start page emits those pages in descending order. |
| **Trigger** | Extraction with `stop < start` (and `stop != -1`) |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ExtractPageRanges` |
| **Outcome** | Pages appended high-to-low, reversing page order |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-17 Successful extraction returns a new succeeded result

| Field | Value |
| --- | --- |
| **Statement** | A successful extraction returns a `PdfResult` in `Succeeded` status wrapping a new in-memory `pdf` document with a fresh identity. |
| **Trigger** | Extraction completes |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ExtractPageRanges`; `PdfAPI.Core.PdfResult` success constructor |
| **Outcome** | `PdfResult.Status = Succeeded`, new `Document` with new `Guid` |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-18 Extraction result records processor and page expression

| Field | Value |
| --- | --- |
| **Statement** | An extraction result records the processor that produced it and echoes the page expression as its message. |
| **Trigger** | Extraction completes |
| **Enforcement** | `PdfAPI.Core.PdfClientService.ExtractPageRanges`; `PdfAPI.Core.DocumentRange.ToString()` |
| **Outcome** | `PdfProcessorType = iTextSharp`; `Message = "Page(s): "` followed by the comma-joined `PageRange` expressions (e.g., `"Page(s): 1-5,7,9-*"`) |
| **Source** | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-19 New documents receive a new Guid identity

| Field | Value |
| --- | --- |
| **Statement** | Every document created through a factory receives a new `Guid` identity. |
| **Trigger** | `Document.Load` / `Document.CreateEmpty` called |
| **Enforcement** | `PdfAPI.Core.Document.Load`, `Document.CreateEmpty` |
| **Outcome** | `Document.Id` set to a new `Guid` |
| **Source** | `PdfAPI.Core\Document.cs` |

---

## BR-20 Missing file load behavior

| Field | Value |
| --- | --- |
| **Statement** | Loading from a path that does not exist fails only when the caller demands existence; otherwise the document is created with no bytes. |
| **Trigger** | `Document.Load(string, bool mustExist)` |
| **Enforcement** | `PdfAPI.Core.Document.Load(string, bool)` |
| **Outcome** | Throws `Exception("The expected file '<path>' does not exist.")`, or returns a document with null `File` |
| **Source** | `PdfAPI.Core\Document.cs` |

---

## BR-21 Document file name convention

| Field | Value |
| --- | --- |
| **Statement** | A document's file name is its identity plus its extension, with leading dots stripped from the supplied extension. |
| **Trigger** | Document construction |
| **Enforcement** | `PdfAPI.Core.Document` constructor |
| **Outcome** | `FileExtension` without leading `.`; `FileName = "{Id}.{FileExtension}"` |
| **Source** | `PdfAPI.Core\Document.cs` |

---

## BR-22 Saving a document writes to destination directory

| Field | Value |
| --- | --- |
| **Statement** | Saving a document writes it into the destination directory under its generated file name and rebinds `FilePath` to that location. |
| **Trigger** | `Document.Save(string)` called |
| **Enforcement** | `PdfAPI.Core.Document.Save` |
| **Outcome** | File written; `FilePath` updated |
| **Source** | `PdfAPI.Core\Document.cs` |

---

## BR-23 Document deletion never throws

| Field | Value |
| --- | --- |
| **Statement** | Deleting a document never throws: failures are swallowed and reported as `false`. |
| **Trigger** | `Document.Delete()` called |
| **Enforcement** | `PdfAPI.Core.Document.Delete` |
| **Outcome** | `true` when deleted, `false` otherwise |
| **Source** | `PdfAPI.Core\Document.cs` |

---

## BR-24 Disposing a document deletes its file

| Field | Value |
| --- | --- |
| **Statement** | Disposing a `Document`, `DocumentRange`, or `PdfResult` deletes the underlying file from disk. |
| **Trigger** | `Dispose()` / `using` block exit |
| **Enforcement** | `PdfAPI.Core.Document.Dispose`, `DocumentRange.Dispose`, `PdfResult.Dispose` |
| **Outcome** | Backing file removed |
| **Source** | `PdfAPI.Core\Document.cs`, `PdfAPI.Core\Extract\DocumentRange.cs`, `PdfAPI.Core\PdfResult.cs` |

---

## BR-25 Page expression token grammar

| Field | Value |
| --- | --- |
| **Statement** | A page expression is a comma-separated list of tokens, each either `n`, `n-m`, `n-*`, or `*`. |
| **Trigger** | `PageRange.Parse(string)` called |
| **Enforcement** | `PdfAPI.Core.PageRange.Parse` |
| **Outcome** | Array of `PageRange` |
| **Source** | `PdfAPI.Core\Extract\PageRange.cs` |

---

## BR-26 Unmatched tokens are skipped silently

| Field | Value |
| --- | --- |
| **Statement** | A token that does not match the page-expression pattern is ignored silently; an entirely unmatched expression yields an empty range array. |
| **Trigger** | Malformed token during parsing |
| **Enforcement** | `PdfAPI.Core.PageRange.Parse` |
| **Outcome** | Token skipped; no exception |
| **Source** | `PdfAPI.Core\Extract\PageRange.cs` |

---

## BR-27 Absent or non-positive stop page is open-ended

| Field | Value |
| --- | --- |
| **Statement** | A range whose stop value is absent, zero, or negative is open-ended (`-1`). |
| **Trigger** | Parsing `n-` or `n-*` |
| **Enforcement** | `PdfAPI.Core.PageRange.Parse` |
| **Outcome** | `StopPage = -1` |
| **Source** | `PdfAPI.Core\Extract\PageRange.cs` |

---

## BR-28 Asterisk means all pages

| Field | Value |
| --- | --- |
| **Statement** | `*` means all pages — start `1`, stop `-1`, flagged as the original range. |
| **Trigger** | Parsing `*`, or default `DocumentRange.Create` |
| **Enforcement** | `PdfAPI.Core.PageRange()` parameterless constructor |
| **Outcome** | `IsOriginal = true` |
| **Source** | `PdfAPI.Core\Extract\PageRange.cs` |

---

## BR-29 Bare page number is a single-page range

| Field | Value |
| --- | --- |
| **Statement** | A bare page number is a single-page range where start equals stop. |
| **Trigger** | Parsing `n`, or `PageRange(int, bool)` with `isSinglePage` true |
| **Enforcement** | `PdfAPI.Core.PageRange(int, bool)` |
| **Outcome** | `IsSingle = true` |
| **Source** | `PdfAPI.Core\Extract\PageRange.cs` |

---

## BR-30 Empty page ranges default to all pages

| Field | Value |
| --- | --- |
| **Statement** | A document range constructed with no page ranges defaults to all pages. |
| **Trigger** | `DocumentRange` created with a null or empty `PageRange[]` |
| **Enforcement** | `PdfAPI.Core.DocumentRange` private constructor |
| **Outcome** | One all-pages `PageRange` |
| **Source** | `PdfAPI.Core\Extract\DocumentRange.cs` |

---

## BR-31 Tracking metadata defaults

| Field | Value |
| --- | --- |
| **Statement** | Tracking metadata defaults to application code `AUTO`, conversion type `Convert`, and not imaged. |
| **Trigger** | `PdfTrackingMetadata.Create` called without those arguments |
| **Enforcement** | `PdfAPI.Core.PdfTrackingMetadata.Create` |
| **Outcome** | Defaults applied |
| **Source** | `PdfAPI.Core\PdfTrackingMetadata.cs` |

---

## BR-32 Tracking prefix conversion type character

| Field | Value |
| --- | --- |
| **Statement** | The first character of the tracking file prefix encodes the conversion type: `A` for `Convert`, `M` for anything else. |
| **Trigger** | Prefix composition |
| **Enforcement** | `PdfAPI.Core.PdfTrackingMetadata.Create` |
| **Outcome** | Prefix character `A` or `M` |
| **Source** | `PdfAPI.Core\PdfTrackingMetadata.cs` |

---

## BR-33 Tracking prefix imaging character

| Field | Value |
| --- | --- |
| **Statement** | The second character of the tracking file prefix encodes imaging intent: `I` when the file should be imaged, otherwise `E`. |
| **Trigger** | Prefix composition |
| **Enforcement** | `PdfAPI.Core.PdfTrackingMetadata.Create` |
| **Outcome** | Prefix character `I` or `E` |
| **Source** | `PdfAPI.Core\PdfTrackingMetadata.cs` |

---

## BR-34 Tracking prefix format

| Field | Value |
| --- | --- |
| **Statement** | The tracking file prefix format is `<type><import><applicationCode>_<fileId>`. |
| **Trigger** | Prefix composition |
| **Enforcement** | `PdfAPI.Core.PdfTrackingMetadata.Create` |
| **Outcome** | `FilePrefix` assigned |
| **Source** | `PdfAPI.Core\PdfTrackingMetadata.cs` |

---

## BR-35 Tracking prefix parsing

| Field | Value |
| --- | --- |
| **Statement** | A tracking prefix is parsed into type, import flag, application code, file id, and an optional trailing file index. |
| **Trigger** | `PdfTrackingMetadata.Parse(string)` called |
| **Enforcement** | `PdfAPI.Core.PdfTrackingMetadata.Parse` |
| **Outcome** | Populated `PdfTrackingMetadata` |
| **Source** | `PdfAPI.Core\PdfTrackingMetadata.cs` |

---

## BR-36 Non-matching prefix yields no metadata

| Field | Value |
| --- | --- |
| **Statement** | A prefix that does not match the tracking pattern yields no metadata. |
| **Trigger** | Non-matching prefix |
| **Enforcement** | `PdfAPI.Core.PdfTrackingMetadata.Parse` |
| **Outcome** | Returns `null` |
| **Source** | `PdfAPI.Core\PdfTrackingMetadata.cs` |

---

## BR-37 Tracking metadata parse defect

| Field | Value |
| --- | --- |
| **Statement** | On parse, imaging intent and conversion type are intended to be recovered from the prefix, but the implementation reads capture groups (`importType`, `conversionType`) that the pattern does not define, so parsed metadata is always `ShouldBeImaged = false` and `ConversionType = Convert`. |
| **Trigger** | `PdfTrackingMetadata.Parse` on any matching prefix |
| **Enforcement** | `PdfAPI.Core.PdfTrackingMetadata.Parse` |
| **Outcome** | Round-trip of `Create` → `Parse` loses imaging intent and conversion type |
| **Source** | `PdfAPI.Core\PdfTrackingMetadata.cs` |

---

## BR-38 Tracking enabled forced by failure email notification

| Field | Value |
| --- | --- |
| **Statement** | Tracking is enabled whenever tracking is not explicitly disabled, and is forced on whenever failure email notification is requested. |
| **Trigger** | `PdfRequest.TrackingEnabled` read |
| **Enforcement** | `PdfAPI.Core.PdfRequest.TrackingEnabled` (getter returns `_trackingEnabled \|\| SendHeatEmailOnFailure`) |
| **Outcome** | `true` even after `TrackingEnabled = false` when `SendHeatEmailOnFailure` is true |
| **Source** | `PdfAPI.Core\Request\PdfRequest.cs` |

---

## BR-39 PDF-A output determination

| Field | Value |
| --- | --- |
| **Statement** | An output is PDF/A only when the requested output format is `PdfA`. |
| **Trigger** | `PdfOptions.IsPdfa` / `PdfResult.OutputType` read |
| **Enforcement** | `PdfAPI.Core.PdfOptions.IsPdfa`; `PdfAPI.Core.PdfResult.OutputType` |
| **Outcome** | `IsPdfa` true; `OutputType` resolves to `"pdfa"` else `"pdf"` |
| **Source** | `PdfAPI.Core\PdfOptions.cs`, `PdfAPI.Core\PdfResult.cs` |

---

## BR-40 Result inherits request identifier and image id

| Field | Value |
| --- | --- |
| **Statement** | A result built from a request inherits the request identifier, and derives `ImageId` by parsing `SourceSystemId` as a number (defaulting to `0` when it is not numeric). |
| **Trigger** | `PdfResult` constructed with a non-null `PdfRequest` |
| **Enforcement** | `PdfAPI.Core.PdfResult(PdfRequest)` private constructor |
| **Outcome** | `RequestId` and `ImageId` set |
| **Source** | `PdfAPI.Core\PdfResult.cs` |

---

## BR-41 PDF-to-PDF conversion skipped by default

| Field | Value |
| --- | --- |
| **Statement** | Conversion of a document that is already a PDF is skipped by default; callers must opt out explicitly. |
| **Trigger** | `ConvertRequest` created |
| **Enforcement** | `PdfAPI.Core.ConvertRequest.SkipPdf` (default `true`); `ConvertRequest.Parse` sets `SkipPdf = true` |
| **Outcome** | Redundant PDF-to-PDF conversion avoided unless `SkipPdf` is set to `false` |
| **Source** | `PdfAPI.Core\Request\ConvertRequest.cs` |

---

## BR-42 Output PDF saved by default

| Field | Value |
| --- | --- |
| **Statement** | A request saves its output PDF by default. |
| **Trigger** | `PdfRequest` created |
| **Enforcement** | `PdfAPI.Core.PdfRequest.SaveOutputPdf` (default `true`) |
| **Outcome** | Output persisted to the configured PDF data directory unless disabled |
| **Source** | `PdfAPI.Core\Request\PdfRequest.cs` |

---

## BR-43 Cache request validity

| Field | Value |
| --- | --- |
| **Statement** | A cache request is valid only when its source identifier is greater than zero. |
| **Trigger** | `PdfCacheRequest.IsValid` read |
| **Enforcement** | `PdfAPI.Core.Cache.PdfCacheRequest.IsValid` |
| **Outcome** | `true` / `false` |
| **Source** | `PdfAPI.Core\Cache\PdfCacheRequest.cs` |

---

## BR-44 Cache result success

| Field | Value |
| --- | --- |
| **Statement** | A cache result is successful only when PDF bytes were returned. |
| **Trigger** | `PdfCacheResult.Success` read |
| **Enforcement** | `PdfAPI.Core.Cache.PdfCacheResult.Success` |
| **Outcome** | `true` / `false` |
| **Source** | `PdfAPI.Core\Cache\PdfCacheResult.cs` |

---

## BR-45 Result status by constructor

| Field | Value |
| --- | --- |
| **Statement** | A result constructed through the failure constructor is marked `Failed`; the success constructor marks it `Succeeded`. |
| **Trigger** | `PdfResult` construction |
| **Enforcement** | `PdfAPI.Core.PdfResult` constructors |
| **Outcome** | `PdfStatus.Failed` or `PdfStatus.Succeeded` |
| **Source** | `PdfAPI.Core\PdfResult.cs` |

## Notes
- Rules [BR-38](#br-38-tracking-enabled-forced-by-failure-email-notification) through [BR-45](#br-45-result-status-by-constructor) are contract-level invariants expressed by PdfAPI.Core types but acted on by consumers (`PdfAPI`, `PdfAPI.SyncFusionCore`, `PdfAPI.Client`); they are listed here because the enforcement point — the property or constructor that computes the value — resides in this library.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Security and authorization](security-authorization.md)
- [API reference](api/index.md)

## Sources
- `PdfAPI.Core\Services\PdfClientService.cs`
- `PdfAPI.Core\Document.cs`
- `PdfAPI.Core\Extract\DocumentRange.cs`
- `PdfAPI.Core\Extract\PageRange.cs`
- `PdfAPI.Core\PdfTrackingMetadata.cs`
- `PdfAPI.Core\Request\PdfRequest.cs`
- `PdfAPI.Core\Request\ConvertRequest.cs`
- `PdfAPI.Core\PdfOptions.cs`
- `PdfAPI.Core\PdfResult.cs`
- `PdfAPI.Core\Cache\PdfCacheRequest.cs`
- `PdfAPI.Core\Cache\PdfCacheResult.cs`
